extern "C"
{
#include <FFmepg/libavutil/opt.h>
#include <FFmepg/libavcodec/avcodec.h>
#include <FFmepg/libavutil/channel_layout.h>
#include <FFmepg/libavutil/common.h>
#include <FFmepg/libavutil/imgutils.h>
#include <FFmepg/libavutil/mathematics.h>
#include <FFmepg/libavutil/samplefmt.h>
#include <FFmepg/libavformat/avformat.h>
#include <FFmepg/libavutil/mem.h>
#include <FFmepg/libavutil/dict.h>
}
#include <SDL/SDL.h>


#include "MusicEngine.h"
#include "Common/Common.h"
#include <algorithm>

#include "NetworkEngine/NetworkFileManager.h"
#include "NetworkEngine/NetworkMusicFile.h"
#include "NetworkEngine/NetworkLyricFile.h"
#include "NetworkMusicStream.h"

using namespace System;
using namespace SPlayer;
using namespace SPlayer::Music;
using namespace SPlayer::Common;

template <class SampleType>
void SDLCALL myAudioCallBack(void *userdata, uint8_t *buffer, int len);

int customRead(void *opaque, uint8_t *buf, int buf_size);
int customWrite(void *opaque, uint8_t *buf, int buf_size);
int64_t customSeek(void *opaque, int64_t offset, int whence);

class SPlayer::Music::MusicEngineInternalVarables
{
public:
    ~MusicEngineInternalVarables();
public:
    AVFormatContext *ic = nullptr;
    unsigned char *iobuffer = nullptr;;
    AVIOContext *iocontext = nullptr;

    AVCodecContext *avctx = nullptr;


    AVFrame *decoded_frame = nullptr;
    int streamIndex = 0;
    int sampleSize = 0;
    AVPacket avpkt;
    
    AVRational timeBase;
    int64_t pts = 0;
    int64_t dts = 0;

    uint8_t audioBuffer[102400 * 2];
    int audioBufferDataSize = 0;

    SDL_AudioSpec want, have;
    SDL_AudioCVT cvt;

    double volume = 1; /* Volume of playing music, between 0 to 1*/
};

ref class MusicInternalStream
{
public:
    static IO::Stream ^iostream = nullptr;
};

SPlayer::Music::MusicEngineInternalVarables::~MusicEngineInternalVarables()
{
    if (iocontext)
    {
        av_free(iocontext);
        iocontext = nullptr;
    }
    if (avctx)
    {
        // this is alloc by avformat_open_input, needn't to be freed.
        avctx = nullptr;
    }
    if (decoded_frame)
    {
        av_frame_free(&decoded_frame);
        decoded_frame = nullptr;
    }
    if (ic)
    {
        avformat_free_context(ic);
        ic = nullptr;
    }
}


SPlayer::Music::MusicEngine::MusicEngine()
{
    vars = new MusicEngineInternalVarables;

    // prepare variables for decoding
    vars->decoded_frame = av_frame_alloc();
}

SPlayer::Music::MusicEngine::~MusicEngine()
{
    stop();
    if (vars)
    {
        delete vars;
        vars = nullptr;
    }
}

SPlayer::Music::MusicEngine::!MusicEngine()
{
    if (vars)
    {
        delete vars;
        vars = nullptr;
    }
}

bool SPlayer::Music::MusicEngine::init()
{
    MusicEngine::currentEngine = gcnew MusicEngine;
    auto vars = currentEngine->vars;

    int err;
    String ^errMsg;
    SDL_SetMainReady();
    err = SDL_Init(SDL_INIT_AUDIO);
    if (err < 0)
    {
        errMsg = Function::cstrToString(SDL_GetError());
        goto _err;
    }

    // registe ffmepg codecs
    avcodec_register_all();
    av_register_all();

    return currentEngine != nullptr;
_err:
    Windows::Forms::MessageBox::Show(errMsg);
    return false;
}

String ^avErrorToString(int err)
{
    char errbuf[128];
    const char *errbuf_ptr = errbuf;

    if (av_strerror(err, errbuf, sizeof(errbuf)) < 0)
        strerror_s(errbuf, AVUNERROR(err));

    return Function::cstrToString(errbuf_ptr);
}

void SPlayer::Music::MusicEngine::stop()
{
    if (hasMusic)
    {
        SDL_CloseAudio();
        if (vars->iocontext)
        {
            av_free(vars->iocontext);
            vars->iocontext = nullptr;
        }
        if (vars->ic)
        {
            avformat_free_context(vars->ic);
            vars->ic = nullptr;
        }

        Function::InvokeSync(eventDispatcher->onStoped);
    }
    return;
}

bool SPlayer::Music::MusicEngine::pause()
{
    SDL_PauseAudio(true);
    Function::InvokeSync(eventDispatcher->onPaused);
    return true;
}

bool SPlayer::Music::MusicEngine::continuePlay()
{
    SDL_PauseAudio(false);
    Function::InvokeSync(eventDispatcher->onContinued);
    return true;
}

bool SPlayer::Music::MusicEngine::play(AVFormatContext *ic_provided)
{
    // get some variables
    auto &ic = vars->ic = ic_provided;
    auto &avctx = vars->avctx;

    int err;
    String ^errMsg;
    
    err = avformat_find_stream_info(ic, nullptr);
    if (err < 0)
    {
        errMsg = avErrorToString(err);
        goto _err;
    }

    AVCodec *codec;
    vars->streamIndex = err = av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, -1, -1, &codec, 0);
    if (err < 0)
    {
        errMsg = avErrorToString(err);
        goto _err;
    }
    if (codec == nullptr)
    {
        errMsg = "Format not supported!";
        goto _err;
    }


    // initialize some temporary variables
    avctx = ic->streams[vars->streamIndex]->codec;
    av_init_packet(&vars->avpkt);
    vars->sampleSize = av_get_bytes_per_sample(avctx->sample_fmt);

    // open codec
    err = avcodec_open2(avctx, codec, nullptr);
    if (err < 0)
    {
        errMsg = avErrorToString(err);
        goto _err;
    }
    err = av_seek_frame(ic, vars->streamIndex, 0, 0);
    if (err < 0)
    {
        errMsg = avErrorToString(err);
        goto _err;
    }

    //----------------------------
    // initialize audio devices
    auto &want = vars->want;
    auto &have = vars->have;
    SDL_zero(want);
    want.freq = avctx->sample_rate;
    want.channels = avctx->channels;
    want.samples = 2048;/* 2048 : Delay at 44100Hz is 46ms */
    want.silence = false;
    want.userdata = vars;
    switch (avctx->sample_fmt)
    {
    case AV_SAMPLE_FMT_U8:
    case AV_SAMPLE_FMT_U8P:
    want.format = AUDIO_S8;
    want.callback = &myAudioCallBack < signed char > ;
    break;
    case    AV_SAMPLE_FMT_S16:
    case    AV_SAMPLE_FMT_S16P:
    want.format = AUDIO_S16;
    want.callback = &myAudioCallBack < signed  short > ;
    break;
    case    AV_SAMPLE_FMT_S32:
    case    AV_SAMPLE_FMT_S32P:
    want.format = AUDIO_S32;
    want.callback = &myAudioCallBack < signed  int > ;
    break;
    case    AV_SAMPLE_FMT_FLT:
    case    AV_SAMPLE_FMT_FLTP:
    want.format = AUDIO_F32;
    want.callback = &myAudioCallBack < float > ;
    break;
    default:
    fprintf(stderr, "Format not supported!");
    break;
    }

    SDL_OpenAudio(&want, &have);

    // prepare conversion param
    SDL_BuildAudioCVT(&vars->cvt, want.format, want.channels, want.freq, have.format, have.channels, have.freq);

    SDL_PauseAudio(false);
    Function::InvokeSync(eventDispatcher->onPlayed);
    return true;
_err:
    Windows::Forms::MessageBox::Show(errMsg);
    return false;
}

bool SPlayer::Music::MusicEngine::playWithFile(System::String ^filename)
{
    int err;
    String ^errMsg;

    auto ic = avformat_alloc_context();
    err = avformat_open_input(&ic, Function::stringToCStr(filename), nullptr, nullptr);
    if (err < 0)
    {
        errMsg = avErrorToString(err);
        goto _err;
    }

    return play(ic);;

_err:
    Windows::Forms::MessageBox::Show(errMsg);
    return false;
}

bool SPlayer::Music::MusicEngine::playWithCustomIOStream(IO::Stream ^iostream, long long duration)
{
    int err;
    String ^errMsg;

    MusicInternalStream::iostream = iostream;
    // init IO context
    size_t audioBufferSize = 4096;
    vars->iobuffer = (unsigned char *)av_malloc(audioBufferSize); // Needn't to be freed?
    vars->iocontext = avio_alloc_context(vars->iobuffer, audioBufferSize, false, nullptr,
                                         &customRead, &customWrite, &customSeek);
    vars->iocontext->seekable = false;
    auto ic = avformat_alloc_context();
    ic->duration = duration;
    ic->pb = vars->iocontext;
    err = avformat_open_input(&ic, nullptr, nullptr, nullptr);
    if (err < 0)
    {
        errMsg = avErrorToString(err);
        goto _err;
    }
    
    return play(ic);;

_err:
    Windows::Forms::MessageBox::Show(errMsg);
    return false;
}


SPlayer::Music::MusicEngineState SPlayer::Music::MusicEngine::state::get()
{
    switch (SDL_GetAudioStatus())
    {
    default:
    case SDL_AUDIO_STOPPED:
    return MusicEngineState::Stoped;
    case SDL_AUDIO_PLAYING:
    return MusicEngineState::Playing;
    case SDL_AUDIO_PAUSED:
    return MusicEngineState::Paused;
    }
}

bool SPlayer::Music::MusicEngine::hasMusic::get()
{
    return SDL_GetAudioStatus() != SDL_AUDIO_STOPPED;
}

unsigned SPlayer::Music::MusicEngine::length::get()
{
    auto stream = vars->ic->streams[vars->streamIndex];
    auto len = static_cast<unsigned>(stream->duration * 1000 * stream->time_base.num / stream->time_base.den);
    return std::max(len, 1u);
}

unsigned SPlayer::Music::MusicEngine::playOffset::get()
{
    auto timeBase = vars->timeBase;
    // TODO : stream->cur_dts is not documented, shall not be used.
    return static_cast<unsigned>(vars->pts * 1000 * timeBase.num / timeBase.den);
}

void SPlayer::Music::MusicEngine::playOffset::set(unsigned offset)
{
    if (this->state == MusicEngineState::Stoped)
        return;
    if (vars->iocontext && vars->iocontext->seekable == false)
        return;

    auto timeBase = vars->timeBase;
    if (timeBase.num < 0)
        return;
    auto target = (int64_t)offset / 1000 * (int64_t)timeBase.den / timeBase.num;
    target = std::min(target, vars->ic->streams[vars->streamIndex]->duration);
    int err = av_seek_frame(vars->ic, vars->streamIndex, target, 0);
}

double SPlayer::Music::MusicEngine::volume::get()
{
    return vars->volume;
}

void SPlayer::Music::MusicEngine::volume::set(double newVolume)
{
    vars->volume = newVolume;
}

inline void pushAudioBuffer(uint8_t *audioBuffer, int &audioBufferDataSize, uint8_t * &buffer, int &len)
{
    int sizeToCopy = std::min(len, audioBufferDataSize);
    memcpy(buffer, audioBuffer, sizeToCopy);
    audioBufferDataSize -= sizeToCopy;
    buffer += sizeToCopy;
    len -= sizeToCopy;
    memmove(audioBuffer, audioBuffer + sizeToCopy, audioBufferDataSize);
}

template <class SampleType>
void SDLCALL myAudioCallBack(void *userdata, uint8_t *buffer, int len)
{
    auto vars = (MusicEngineInternalVarables *)userdata;
    auto ic = vars->ic;
    auto avctx = vars->avctx;
    auto sample_size = vars->sampleSize;

    auto &avpkt = vars->avpkt;
    auto &decoded_frame = vars->decoded_frame;
    auto &audioBuffer = vars->audioBuffer;
    auto &audioBufferDataSize = vars->audioBufferDataSize;
    auto &cvt = vars->cvt;

    // copy audioBuffer into it
    pushAudioBuffer(audioBuffer, audioBufferDataSize, buffer, len);

    int readResult;
    bool first = false;
    while (len > 0 && (readResult = av_read_frame(ic, &avpkt)) == 0)
    {
        if (!first)
        {
            first = true;
            vars->pts = avpkt.pts;
            vars->dts = avpkt.dts;
            vars->timeBase = ic->streams[vars->streamIndex]->time_base;
        }
        // need more data;
        int got_frame;
        int res = avcodec_decode_audio4(avctx, decoded_frame, &got_frame, &avpkt);
        if (res < 0) continue;
        if (got_frame)
        {
            double volume = vars->volume;
            auto pAudio = reinterpret_cast<SampleType *>(audioBuffer);
            for (int s = 0; s < decoded_frame->nb_samples; s++)
            {
                for (int ch = 0; ch < avctx->channels; ch++)
                {
                    *pAudio++ = static_cast<SampleType>(volume *
                                                        *reinterpret_cast<SampleType *>(&decoded_frame->data[ch][sample_size * s]));
                }
            }
            cvt.len = cvt.len_cvt = decoded_frame->nb_samples * decoded_frame->channels * sample_size;
            cvt.buf = audioBuffer;
            if (cvt.needed)
            {
                SDL_ConvertAudio(&cvt);
            }

            audioBufferDataSize = cvt.len_cvt;
            pushAudioBuffer(audioBuffer, audioBufferDataSize, buffer, len);
        }
    }
    if (readResult < 0)
    {
        auto method = MusicEngine::currentEngine->eventDispatcher->onFinished;
        //dynamic_cast<Windows::Forms::Form ^>(method->Target)->BeginInvoke(method);
        Function::InvokeSync(method);
    }
}

int customRead(void *opaque, uint8_t *buf, int buf_size)
{
    auto stream = MusicInternalStream::iostream;
    auto arr = gcnew array<unsigned char>(buf_size);
    int nread = stream->Read(arr, 0, buf_size);

    System::Runtime::InteropServices::Marshal::Copy(arr, 0, static_cast<System::IntPtr>(buf), buf_size);
    return nread;
}
int customWrite(void *opaque, uint8_t *buf, int buf_size)
{
    return 0;
}
int64_t customSeek(void *opaque, int64_t offset, int whence)
{
    auto stream = MusicInternalStream::iostream;

    IO::SeekOrigin origin = IO::SeekOrigin::Begin;
    if (whence == SEEK_CUR)
    {
        origin = IO::SeekOrigin::Current;
    }
    else if (whence == SEEK_END)
    {
        origin = IO::SeekOrigin::End;
    }

    return stream->Seek(offset, origin);
}